'''
This module defines the basic command structure, and provides a number of
classes which work with commands.
'''

from PyQt4.QtGui import *

import copy

# ---------------------------------------------------------------------------
#  Some functions that control the submission of commands and command groups
# ---------------------------------------------------------------------------

class UndoStack(QUndoStack):

   __instance = None

   def __new__(cls, *args, **kwargs):
      if cls.__instance is None:
         cls.__instance = QUndoStack.__new__(cls)
      return cls.__instance

beginMacro = UndoStack().beginMacro
endMacro   = UndoStack().endMacro
_submit    = UndoStack().push

# ---------------------------------------------------------------------------
#  General commands that are not used by themselves but provide a basis
#  for more specialised commands
# ---------------------------------------------------------------------------

class _Command(QUndoCommand):
   ''' Customized base class for all commands.
   The local variables doc, success and will_change_state are defined. After
   the command's Do() method is called, success should contain a value reporting
   whether the command was successful or not. will_change_state determines
   whether the gui will be modified when this is called.
   '''
   def __init__(self, name):
      QUndoCommand.__init__(self, name)
      self.success = False


class AddCommand(_Command):
   ''' Add an item to a sequence at position. If position is None, the item
   will be appended. '''

   def __init__(self, name, sequence, position, item):
      _Command.__init__(self, name)
      self.seq = sequence
      self.psn = position if position is not None else len(sequence)
      self.item = item
      _submit(self)

   def redo(self):
      self.seq.insert(self.psn, self.item)

   def undo(self):
      self.seq.pop(self.psn)

class DeleteCommand(_Command):
   ''' Delete the item at index from a sequence.'''
   def __init__(self, name, sequence, index):
      _Command.__init__(self, name)
      self.seq = sequence
      self.psn = index
      self.item = sequence[index]
      _submit(self)

   def redo(self):
      self.seq.pop(self.psn)

   def undo(self):
      self.seq.insert(self.psn, self.item)


class ChangeCommand(_Command):
   '''Change and item in a sequence.'''

   def __init__(self, name, sequence, index, value):
      _Command.__init__(self, name)
      self.seq = sequence
      self.index = index
      self.value = value
      self.oldvalue = sequence[index]
      _submit(self)

   def redo(self):
      self.seq[self.index] = self.value

   def undo(self):
      self.seq[self.index] = self.oldvalue


class ChangeDictCommand(_Command):
   '''Change an item in a dict.'''

   def __init__(self, name, sequence, index, value):
      _Command.__init__(self, name)
      self.seq = sequence
      self.index = index
      self.value = value
      if index in sequence:
         self.oldvalue = sequence[index]
      _submit(self)

   def redo(self):
      self.seq[self.index] = self.value

   def undo(self):
      if hasattr(self, 'oldvalue'):
         self.seq[self.index] = self.oldvalue
      else:
         del self.seq[self.index]


class MoveCommand(_Command):
   '''Moves an item in a sequence from index the specified distance down. If
   distance is negative, it will be moved up in the sequence.'''

   def __init__(self, name, sequence, distance, index):
      _Command.__init__(self, name)
      self.seq = sequence
      self.oldpsn = sequence.index(id) if type(id) is itemtype else id
      self.newpsn = self.oldpsn + distance
      self.item = id if type(id) is itemtype else sequence[self.psn]
      _submit(self)

   def redo(self):
      move(self.seq, oldpsn, newpsn)

   def undo(self):
      move(self.seq, newpsn, oldpsn)


class ChangeAttrCommand(_Command):
   ''' Change the value of an object's attribute.
   e.g. ChangeAttrCommand(..., someclass, 'data', 42) is equivalent to
   someclass.data = 42 '''

   def __init__(self, name, obj, attr, value):
      _Command.__init__(self, name)
      self.obj = obj
      self.attr = attr
      self.new = value
      self.old = getattr(obj, attr)
      _submit(self)

   def redo(self):
      setattr(self.obj, self.attr, self.new)

   def undo(self):
      setattr(self.obj, self.attr, self.old)
